/***************************************************************/
//map has a 2d array of MapPoints, each with a type. Controls the...well, the map. Oh, and the enemies
// Tyler Wozniak & Andrew Hall
/***************************************************************/

#include "Map.h"

//SHALLOW copy constructor, because you should never make unique copies of the map
Map::Map(const Map& other)
{
	this->enemies = other.enemies;
	this->mapArray = other.mapArray;
}

//Creates a map
Map::Map(void)
{
	mapArray = new MapPoint*[MAX_MAP];
	for(int i = 0; i < MAX_MAP; i++)
	{
		mapArray[i] = new MapPoint[MAX_MAP];
	}
	initializeMap("map.txt");
	populateMap();
}

//Initializes the map with the file
void Map::initializeMap( string inpPath )
{
	//read in the map file
	int mapX = 0, mapY = 0;
	string line;
	int currentLine[MAX_MAP];
	int newMap[MAX_MAP][MAX_MAP];
	ifstream myfile ("../Inits/map.txt");
	if (myfile.is_open())
	{
		while ( myfile.good() )
		{
			
			getline (myfile,line);
			if (line.length() > mapX)	//mapX is the largest string value
				mapX = line.length();
			//function to add in walls if lines aren't long enough here
			for (int i = 0; i < mapX; i++)
			{
				currentLine[i] = atoi(line.substr(i, 1).c_str());
				newMap[i][mapY] = atoi(line.substr(i, 1).c_str());
			}
			
			//cout << line << endl;
			mapY++;
		}
		myfile.close();
	}
	else cout << "Unable to open file"; 

	for(int i = 0; i < MAX_MAP; i++)
	{
		for(int j = 0; j < MAX_MAP; j++)
		{
			mapArray[i][j] = newMap[i][j];
		}
	}
}

//Returns a map point at a given row and column
Map::MapPoint Map::getMapPoint( int row, int col ) const
{
	return mapArray[row][col];
}

//Updates the visible field on each mappoint.
//Uses bresenham's line algorithm http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
void Map::updateVisible(int heroRow, int heroCol)
{
	//First, clear the map of any current visibility
	for(int i = 0; i < MAX_MAP; i++)
	{
		for(int j = 0; j < MAX_MAP; j++)
		{
			mapArray[i][j].visible = false;
		}
	}

	//So now we save some time by only updating within the square of max sight
	for(int i = heroRow - MAX_SIGHT; i < heroRow + MAX_SIGHT + 1; i++)
	{
		for(int j = heroCol - MAX_SIGHT; j < heroCol + MAX_SIGHT + 1; j++)
		{
			if(i >= 0 && j >= 0 && i < MAX_MAP && j < MAX_MAP)
			{
				//Now we only actually raytrace within the view bounds so check:
				if(((i - heroRow)*(i - heroRow)) + ((j - heroCol)*(j - heroCol)) < MAX_SIGHT_SQUARED)
				{
					//WE'RE GONNA IMPLEMENT BRESENHAM'S LINE DRAWING ALGORITHM
					//HERE WE GO...
					//Assume visible to start
					mapArray[i][j].visible = true;
					//Set up some variables that can be modified and worked with:
					int y1 = i;
					int x1 = j;
					int y2 = heroRow;
					int x2 = heroCol;
					boolean steep = (AbsoluteValue(y1 - y2) > AbsoluteValue(x1 - x2)); //Check if the slope of the line is steep

					//If it's steep, we swap the x's and y's so that we work with a line
					//where the X's move more often than Y
					if(steep)
					{
						int tmp = x1;
						x1 = y1;
						y1 = tmp;
						tmp = x2;
						x2 = y2;
						y2 = tmp;
					}
					//Now, if the first X is larger than the second x, we just switch them around
					//Keeping the line's slope but iterating through a different direction.
					if(x1 > x2)
					{
						int tmp = x1;
						x1 = x2;
						x2 = tmp;
						tmp = y1;
						y1 = y2;
						y2 = tmp;
					}

					int deltaX = x2 - x1; //This is your change in x!
					int deltaY = AbsoluteValue(y2 - y1); //And your change in y (positive)
					int error = deltaX / 2; //Error represents how far the x has to change before we step
					int yStep; //yStep is the amount you are going to step for a new y
					int y = y1; //Set your y to your starting y

					if(y1 < y2)
					{
						yStep = 1;
					}
					else
					{
						yStep = -1;
					}
					
					for(int x = x1; x < x2; x++)
					{
						if(steep)
						{
							if(mapArray[x][y].solid && (x != i || y != j))
							{
								mapArray[i][j].visible = false;
							}
						}
						else
						{
							if(mapArray[y][x].solid && (y != i || x != j))
							{
								mapArray[i][j].visible = false;
							}
						}
						//So we move the error by the change in Y and if it gets to 0, you step
						error -= deltaY;
						if(error < 0)
						{
							y += yStep;
							error += deltaX;
						}
					}
					

					//I thought this was redundant code and you could just set seen = to visible, 
					//but seen should never be set to false after it's been set to true
					if(mapArray[i][j].visible)
						mapArray[i][j].seen = true;
				}
			}
		}
	}
}

//sets the point at the given row and column's seen value to true.
void Map::setSeen(int row, int col)
{
	mapArray[row][col].seen = true;
}

//Destructor
Map::~Map(void)
{
	for(int i = 0; i < MAX_MAP; i++)
	{
		delete mapArray[i];
	}
	delete mapArray;
}

//Returns an integer to identify whether a tile is empty (-1), solid(-2), or else returns the index of the enemy in the enemies array
int Map::checkPosition( int row, int col ) const
{
	if(row < 0 || row > MAX_MAP || col < 0 || col > MAX_MAP) //If it's out of bounds, consider it solid
		return -2;
	if(getMapPoint(row,col).solid) //If it's solid, it's solid
		return -2;
	else
	{
		for(int i = 0; i < enemies.Length(); i++)
		{
			if(enemies[i]->getX() == col && enemies[i]->getY() == row)
				return i;
		}
		return -1;
	}
}

//Returns an enemy at a given index
Enemy* Map::getEnemy(int index) const
{
	return enemies[index];
}

//Kills an enemy and removes it from the List
void Map::killEnemy( Enemy* enemy )
{
	enemies -= enemy;
}

//Returns a vector with the pointers of enemies within range rng of player x and player y
vector<Unit*> Map::getNearbyEnemyPtrs(int rng, int playX, int playY)
{
	vector<Unit*> vecToReturn;
	for(int i = 0; i < enemies.Length(); i++) //Iterate through all enemies
	{
		Enemy* curEn = enemies[i]; //Store current enemy pointer for convenience
		//Now, if it's within the range in both X and Y, it'll be added to the return vector
		if(((curEn->getX()) > (playX - rng) && (curEn->getX()) < (playX + rng)) && ((curEn->getY()) > (playY - rng) && (curEn->getY()) < (playY + rng)) && curEn->getSeen())
		{
			vecToReturn.push_back(curEn);
		}
	}
	return vecToReturn;
}

//Populates the map based on an XML of enemies
void Map::populateMap()
{
	tinyxml2::XMLDocument doc;
	doc.LoadFile("../Inits/Pop.xml");
	if(!doc.ErrorID())
	{
		tinyxml2::XMLElement* enemyEl = doc.FirstChildElement("enemies")->FirstChildElement();
		string enType = enemyEl->Attribute("type");
		int x = enemyEl->IntAttribute("x");
		int y = enemyEl->IntAttribute("y");
		if(!enType.empty() && x && y)
			enemies += new Enemy(enType,x,y);
		while(enemyEl->NextSiblingElement())
		{
			enemyEl = enemyEl->NextSiblingElement();
			string enType = enemyEl->Attribute("type");
			int x = enemyEl->IntAttribute("x");
			int y = enemyEl->IntAttribute("y");
			if(!enType.empty() && x && y)
				enemies += new Enemy(enType,x,y);
		}
	}
	else
		enemies += new Enemy("char",5,10);
}

//Returns whether the enemies are all dead
bool Map::noMoreEnemies()
{
	return !enemies.Length();
}
